using System;
using System.Globalization;
using NFreeMarker.Template;
using Number = NFreeMarker.Template.Utility.Number;

namespace NFreeMarker.Core
{
    /**
     * A holder for builtins that operate exclusively on TemplateSequenceModels.
     */

    internal abstract class NumericalBuiltins
    {
        internal abstract class NumberBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                return CalculateResult(EvaluationUtil.GetNumber(model, Target, env), model);
            }

            protected abstract ITemplateModel CalculateResult(Number num, ITemplateModel model);
        }

        internal class ByteBuiltIn : NumberBuiltIn
        {
            protected override ITemplateModel CalculateResult(Number num, ITemplateModel model)
            {
                if (num is Number.ByteNumber)
                {
                    return model;
                }
                return new SimpleNumber(num.ByteValue());
            }
        }

        internal class ShortBuiltIn : NumberBuiltIn
        {
            protected override ITemplateModel CalculateResult(Number num, ITemplateModel model)
            {
                if (num is Number.ShortNumber)
                {
                    return model;
                }
                return new SimpleNumber(num.ShortValue());
            }
        }

        internal class IntBuiltIn : NumberBuiltIn
        {
            protected override ITemplateModel CalculateResult(Number num, ITemplateModel model)
            {
                if (num is Number.IntNumber)
                {
                    return model;
                }
                return new SimpleNumber(num.IntValue());
            }
        }

        internal class LongBuiltIn : NumberBuiltIn
        {
            protected override ITemplateModel CalculateResult(Number num, ITemplateModel model)
            {
                if (num is Number.LongNumber)
                {
                    return model;
                }
                return new SimpleNumber(num.LongValue());
            }
        }

        internal class FloatBuiltIn : NumberBuiltIn
        {
            protected override ITemplateModel CalculateResult(Number num, ITemplateModel model)
            {
                if (num is Number.FloatNumber)
                {
                    return model;
                }
                return new SimpleNumber(num.FloatValue());
            }
        }

        internal class DoubleBuiltIn : NumberBuiltIn
        {
            protected override ITemplateModel CalculateResult(Number num, ITemplateModel model)
            {
                if (num is Number.DoubleNumber)
                {
                    return model;
                }
                return new SimpleNumber(num.DoubleValue());
            }
        }

        internal class FloorBuiltIn : NumberBuiltIn
        {
            protected override ITemplateModel CalculateResult(Number num, ITemplateModel model)
            {
                return new SimpleNumber(Math.Floor(num.DoubleValue()));
            }
        }

        internal class CeilingBuiltIn : NumberBuiltIn
        {
            protected override ITemplateModel CalculateResult(Number num, ITemplateModel model)
            {
                return new SimpleNumber(Math.Ceiling(num.DoubleValue()));
            }
        }

        internal class RoundBuiltIn : NumberBuiltIn
        {
            protected override ITemplateModel CalculateResult(Number num, ITemplateModel model)
            {
                return new SimpleNumber(Math.Round(num.DoubleValue()));
            }
        }

        // Doesn't extend NumberBuiltIn because "calculateResult" would need the Environment.
        internal class CBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                Number num = EvaluationUtil.GetNumber(model, Target, env);
                if (num is Number.IntNumber)
                {
                    // We accelerate this fairly common case
                    return new SimpleScalar(num.ToString());
                }
                return new SimpleScalar(
                    num.DecimalValue().ToString(
                        env.GetCNumberFormat(),
                        CultureInfo.InvariantCulture));
            }
        }
    }
}